/* rt2x00core.c
 *
 * Copyright (C) 2004 - 2005 rt2x00-2.0.0-b3 SourceForge Project
 *			     <http://rt2x00.serialmonkey.com>
 *               2006        rtnet adaption by Daniel Gregorek
 *                           <dxg@gmx.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Module: rt2x00core
 * Abstract: rt2x00 core routines.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <asm/io.h>

#include <rtnet_port.h>

#include "rt2x00.h"

#ifdef DRV_NAME
#undef DRV_NAME
#define DRV_NAME "rt_rt2x00core"
#endif /* DRV_NAME */

static int rt2x00_radio_on(struct _rt2x00_core *core);
static int rt2x00_radio_off(struct _rt2x00_core *core);

static int cards[MAX_UNITS] = { [0 ...(MAX_UNITS - 1)] = 1 };
module_param_array(cards, int, NULL, 0444);
MODULE_PARM_DESC(cards, "array of cards to be supported (e.g. 1,0,1)");

/*
 * Writes the pending configuration to the device
 */
static void rt2x00_update_config(struct _rt2x00_core *core)
{
	u16 update_flags = 0x0000;

	if (!test_bit(DEVICE_ENABLED, &core->flags) &&
	    !test_bit(DEVICE_RADIO_ON, &core->flags))
		return;

	if (test_and_set_bit(DEVICE_CONFIG_UPDATE, &core->flags))
		return;

	update_flags = core->config.update_flags;
	core->config.update_flags = 0;

	if (likely(update_flags))
		core->handler->dev_update_config(core, update_flags);

	clear_bit(DEVICE_CONFIG_UPDATE, &core->flags);
}

/*
 * Radio control.
 */
static int rt2x00_radio_on(struct _rt2x00_core *core)
{
	int status = 0x00000000;

	if (test_bit(DEVICE_RADIO_ON, &core->flags)) {
		WARNING("Radio already on.\n");
		return -ENOTCONN;
	}

	status = core->handler->dev_radio_on(core);
	if (status)
		return status;

	set_bit(DEVICE_RADIO_ON, &core->flags);

	return 0;
}

static int rt2x00_radio_off(struct _rt2x00_core *core)
{
	if (!test_and_clear_bit(DEVICE_RADIO_ON, &core->flags)) {
		WARNING("Radio already off.\n");
		return -ENOTCONN;
	}

	core->handler->dev_radio_off(core);

	return 0;
}

/*
 * user space io handler
 */
static int rt2x00_ioctl(struct rtnet_device *rtnet_dev, struct ifreq *ifr,
			int request)
{
	struct rtwlan_device *rtwlan_dev = rtnetdev_priv(rtnet_dev);
	struct _rt2x00_core *core = rtwlan_priv(rtwlan_dev);
	struct rtwlan_cmd *cmd;
	u8 rate, dsss_rate, ofdm_rate;
	u32 address, value;

	cmd = (struct rtwlan_cmd *)ifr->ifr_data;

	switch (request) {
	case IOC_RTWLAN_IFINFO:
		cmd->args.info.bitrate = core->config.bitrate;
		cmd->args.info.channel = core->config.channel;
		cmd->args.info.retry = core->config.short_retry;
		cmd->args.info.txpower = core->config.txpower;
		cmd->args.info.bbpsens = core->config.bbpsens;
		cmd->args.info.mode = core->rtwlan_dev->mode;
		cmd->args.info.rx_packets = core->rtwlan_dev->stats.rx_packets;
		cmd->args.info.tx_packets = core->rtwlan_dev->stats.tx_packets;
		cmd->args.info.tx_retry = core->rtwlan_dev->stats.tx_retry;
		cmd->args.info.autoresponder =
			core->config.config_flags & CONFIG_AUTORESP ? 1 : 0;
		cmd->args.info.dropbcast =
			core->config.config_flags & CONFIG_DROP_BCAST ? 1 : 0;
		cmd->args.info.dropmcast =
			core->config.config_flags & CONFIG_DROP_MCAST ? 1 : 0;
		DEBUG("rtwlan_dev->mode=%d\n", rtwlan_dev->mode);
		break;
	case IOC_RTWLAN_BITRATE:
		rate = cmd->args.set.bitrate;
		ofdm_rate = ieee80211_is_ofdm_rate(rate);
		dsss_rate = ieee80211_is_dsss_rate(rate);
		DEBUG("bitrate=%d\n", rate);
		if (!(dsss_rate ^ ofdm_rate))
			NOTICE("Rate %d is not DSSS and not OFDM.\n", rate);
		core->config.bitrate = rate;
		core->config.update_flags |= UPDATE_BITRATE;
		break;
	case IOC_RTWLAN_CHANNEL:
		DEBUG("channel=%d\n", cmd->args.set.channel);
		core->config.channel = cmd->args.set.channel;
		core->config.update_flags |= UPDATE_CHANNEL;
		break;
	case IOC_RTWLAN_RETRY:
		core->config.short_retry = cmd->args.set.retry;
		core->config.update_flags |= UPDATE_RETRY;
		break;
	case IOC_RTWLAN_TXPOWER:
		core->config.txpower = cmd->args.set.txpower;
		core->config.update_flags |= UPDATE_TXPOWER;
		break;
	case IOC_RTWLAN_AUTORESP:
		if (cmd->args.set.autoresponder)
			core->config.config_flags |= CONFIG_AUTORESP;
		else
			core->config.config_flags &= ~CONFIG_AUTORESP;
		core->config.update_flags |= UPDATE_AUTORESP;
		break;
	case IOC_RTWLAN_DROPBCAST:
		if (cmd->args.set.dropbcast)
			core->config.config_flags |= CONFIG_DROP_BCAST;
		else
			core->config.config_flags &= ~CONFIG_DROP_BCAST;
		core->config.update_flags |= UPDATE_PACKET_FILTER;
		break;
	case IOC_RTWLAN_DROPMCAST:
		if (cmd->args.set.dropmcast)
			core->config.config_flags |= CONFIG_DROP_MCAST;
		else
			core->config.config_flags &= ~CONFIG_DROP_MCAST;
		core->config.update_flags |= UPDATE_PACKET_FILTER;
		break;
	case IOC_RTWLAN_TXMODE:
		core->rtwlan_dev->mode = cmd->args.set.mode;
		break;
	case IOC_RTWLAN_BBPSENS:
		value = cmd->args.set.bbpsens;
		if (value < 0)
			value = 0;
		if (value > 127)
			value = 127;
		core->config.bbpsens = value;
		core->config.update_flags |= UPDATE_BBPSENS;
		break;
	case IOC_RTWLAN_REGREAD:
	case IOC_RTWLAN_BBPREAD:
		address = cmd->args.reg.address;
		core->handler->dev_register_access(core, request, address,
						   &value);
		cmd->args.reg.value = value;
		break;
	case IOC_RTWLAN_REGWRITE:
	case IOC_RTWLAN_BBPWRITE:
		address = cmd->args.reg.address;
		value = cmd->args.reg.value;
		core->handler->dev_register_access(core, request, address,
						   &value);
		break;
	default:
		ERROR("Unknown request!\n");
		return -1;
	}

	if (request != IOC_RTWLAN_IFINFO)
		rt2x00_update_config(core);

	return 0;
}

/*
 * TX/RX related routines.
 */
static int rt2x00_start_xmit(struct rtskb *rtskb,
			     struct rtnet_device *rtnet_dev)
{
	struct rtwlan_device *rtwlan_dev = rtnetdev_priv(rtnet_dev);
	struct _rt2x00_core *core = rtwlan_priv(rtwlan_dev);
	u16 xmit_flags = 0x0000;
	u8 rate = 0x00;

	if (unlikely(rtskb)) {
		rate = core->config.bitrate;
		if (ieee80211_is_ofdm_rate(rate))
			xmit_flags |= XMIT_OFDM;

		/* Check if the packet should be acknowledged */
		if (core->rtwlan_dev->mode == RTWLAN_TXMODE_ACK)
			xmit_flags |= XMIT_ACK;

		if (core->handler->dev_xmit_packet(core, rtskb, rate,
						   xmit_flags))
			ERROR("Packet dropped !");

		dev_kfree_rtskb(rtskb);
	}

	return 0;
}

/***
 *  rt2x00_open
 *  @rtdev
 */
static int rt2x00_open(struct rtnet_device *rtnet_dev)
{
	struct rtwlan_device *rtwlan_dev = rtnetdev_priv(rtnet_dev);
	struct _rt2x00_core *core = rtwlan_priv(rtwlan_dev);
	int status = 0x00000000;

	DEBUG("Start.\n");

	if (test_and_set_bit(DEVICE_ENABLED, &core->flags)) {
		ERROR("device already enabled.\n");
		return -EBUSY;
	}

	/*
     * Start rtnet interface.
     */
	rt_stack_connect(rtnet_dev, &STACK_manager);

	status = rt2x00_radio_on(core);
	if (status) {
		clear_bit(DEVICE_ENABLED, &core->flags);
		ERROR("Couldn't activate radio.\n");
		return status;
	}

	core->config.led_status = 1;
	core->config.update_flags |= UPDATE_LED_STATUS;
	rt2x00_update_config(core);

	rtnetif_start_queue(rtnet_dev);

	DEBUG("Exit success.\n");

	return 0;
}

/***
 *  rt2x00_close
 *  @rtdev
 */
static int rt2x00_close(struct rtnet_device *rtnet_dev)
{
	struct rtwlan_device *rtwlan_dev = rtnetdev_priv(rtnet_dev);
	struct _rt2x00_core *core = rtwlan_priv(rtwlan_dev);

	DEBUG("Start.\n");

	if (!test_and_clear_bit(DEVICE_ENABLED, &core->flags)) {
		ERROR("device already disabled.\n");
		return -EBUSY;
	}

	rt2x00_radio_off(core);

	rtnetif_stop_queue(rtnet_dev);
	rt_stack_disconnect(rtnet_dev);

	return 0;
}

/*
 * Initialization handlers.
 */
static void rt2x00_init_config(struct _rt2x00_core *core)
{
	DEBUG("Start.\n");

	memset(&core->config.bssid, '\0', sizeof(core->config.bssid));

	core->config.channel = 1;
	core->config.bitrate = capabilities.bitrate[0];
	core->config.bbpsens = 50;
	core->config.config_flags = 0;
	core->config.config_flags |=
		CONFIG_DROP_BCAST | CONFIG_DROP_MCAST | CONFIG_AUTORESP;
	core->config.short_retry = 4;
	core->config.long_retry = 7;
	core->config.txpower = 100;
	core->config.plcp = 48;
	core->config.sifs = 10;
	core->config.slot_time = 20;
	core->rtwlan_dev->mode = RTWLAN_TXMODE_RAW;
	core->config.update_flags = UPDATE_ALL_CONFIG;
}

struct rtnet_device *rt2x00_core_probe(struct _rt2x00_dev_handler *handler,
				       void *priv, u32 sizeof_dev)
{
	struct rtnet_device *rtnet_dev = NULL;
	struct _rt2x00_core *core = NULL;
	struct rtwlan_device *rtwlan_dev = NULL;
	static int cards_found = -1;
	int err;

	DEBUG("Start.\n");

	cards_found++;
	if (cards[cards_found] == 0)
		goto exit;

	rtnet_dev =
		rtwlan_alloc_dev(sizeof_dev + sizeof(*core), RX_ENTRIES * 2);
	if (!rtnet_dev)
		goto exit;

	rt_rtdev_connect(rtnet_dev, &RTDEV_manager);
	rtnet_dev->vers = RTDEV_VERS_2_0;

	rtwlan_dev = rtnetdev_priv(rtnet_dev);
	memset(rtwlan_dev, 0x00, sizeof(*rtwlan_dev));

	core = rtwlan_priv(rtwlan_dev);
	memset(core, 0x00, sizeof(*core));

	core->rtwlan_dev = rtwlan_dev;
	core->handler = handler;
	core->priv = (void *)core + sizeof(*core);
	core->rtnet_dev = rtnet_dev;

	/* Set configuration default values. */
	rt2x00_init_config(core);

	if (core->handler->dev_probe && core->handler->dev_probe(core, priv)) {
		ERROR("device probe failed.\n");
		goto exit;
	}
	INFO("Device " MAC_FMT " detected.\n", MAC_ARG(rtnet_dev->dev_addr));

	rtwlan_dev->hard_start_xmit = rt2x00_start_xmit;

	rtnet_dev->open = &rt2x00_open;
	rtnet_dev->stop = &rt2x00_close;
	rtnet_dev->do_ioctl = &rt2x00_ioctl;
	rtnet_dev->hard_header = &rt_eth_header;

	if ((err = rt_register_rtnetdev(rtnet_dev)) != 0) {
		rtdev_free(rtnet_dev);
		ERROR("rtnet_device registration failed.\n");
		printk("err=%d\n", err);
		goto exit_dev_remove;
	}

	set_bit(DEVICE_AWAKE, &core->flags);

	return rtnet_dev;

exit_dev_remove:
	if (core->handler->dev_remove)
		core->handler->dev_remove(core);

exit:
	return NULL;
}
EXPORT_SYMBOL_GPL(rt2x00_core_probe);

void rt2x00_core_remove(struct rtnet_device *rtnet_dev)
{
	rt_unregister_rtnetdev(rtnet_dev);
	rt_rtdev_disconnect(rtnet_dev);

	rtdev_free(rtnet_dev);
}
EXPORT_SYMBOL_GPL(rt2x00_core_remove);

/*
 * RT2x00 core module information.
 */
static char version[] = DRV_NAME " - " DRV_VERSION;

MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION("RTnet rt2500 PCI WLAN driver (Core Module)");
MODULE_LICENSE("GPL");

static int __init rt2x00_core_init(void)
{
	printk(KERN_INFO "Loading module: %s\n", version);
	return 0;
}

static void __exit rt2x00_core_exit(void)
{
	printk(KERN_INFO "Unloading module: %s\n", version);
}

module_init(rt2x00_core_init);
module_exit(rt2x00_core_exit);
